package com.un.ebs.sys.service.impl;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.un.ebs.businessObject.service.DbService;
import com.un.ebs.core.enums.DataTypeEnum;
import com.un.ebs.core.enums.RequestArgEnum;
import com.un.ebs.core.enums.ServiceTypeEnum;
import com.un.ebs.core.metadata.*;
import com.un.ebs.sys.domain.Function;
import com.un.ebs.sys.service.ScriptService;
import org.luaj.vm2.LuaTable;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.lib.jse.CoerceJavaToLua;
import org.luaj.vm2.lib.jse.JsePlatform;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMethod;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;
import java.util.stream.Collectors;

@SuppressWarnings("ALL")
@Service
public class ScriptServiceImpl implements ScriptService, ServiceExt {

    @Autowired
    DbService dbService;

    private final Map<String, LuaValue> pool = new HashMap<>();
    private final Map<String, LuaValue> includePool = new HashMap<>();

    @Override
    public void init(List<Function> list) {
        if (list.size() != 0) {
            list.stream()
                    .sorted(Comparator.comparing(Function::getVersionNo).reversed())
                    .filter(distinctByKey(Function::getFunName))
                    .filter(Function::getReleaseStatus)
                    .forEach(x -> this.add(x.getFunName(), x.getFunContext()));
        }

        includePool.put("dbService", CoerceJavaToLua.coerce(dbService));
    }

    private static <T> Predicate<T> distinctByKey(java.util.function.Function<? super T, ?> key) {
        Map<Object, Boolean> seen = new ConcurrentHashMap<>();
        return t -> seen.putIfAbsent(key.apply(t), Boolean.TRUE) == null;
    }

    private LuaValue load(String content) {
        try {
            return JsePlatform.standardGlobals().load(content);
        } catch (Exception e) {
            return null;
        }
    }

    @Override
    public boolean add(String name, String content) {
        if (pool.containsKey(name)) {
            return false;
        }
        LuaValue runner = this.load(content);
        if (runner != null) {
            pool.put(name, runner);
            return true;
        }
        return false;
    }

    @Override
    public boolean del(String name) {
        if (!pool.containsKey(name)) {
            return false;
        }
        pool.remove(name);
        return true;
    }

    @Override
    public boolean update(String name, String content) {
        if (pool.containsKey(name)) {
            return false;
        }
        this.del(name);
        return this.add(name, content);
    }

    @Override
    public boolean has(String name) {
        return this.pool.containsKey(name);
    }

    @Override
    public Map call(String name, Map params) {
        LuaValue func = pool.get(name).call();
        LuaTable result = func.get(LuaValue.valueOf("main")).call(
                this.mapInclude(func.get(LuaValue.valueOf("include")).call().checktable()),
                this.toLuaTable(JSONObject.parseObject(JSONObject.toJSONString(params)))
        ).checktable();

        return JSONObject.toJavaObject(this.toJSONObject(result), Map.class);
    }

    private JSONObject toJSONObject(LuaTable table) {
        JSONObject obj = new JSONObject();
        if (table != null) {
            LuaValue[] keys = table.keys();
            if (keys != null && keys.length > 0) {
                try {
                    for (LuaValue luaValue : keys) {
                        String key = luaValue.optjstring("");
                        LuaValue value = table.get(luaValue);
                        if (value instanceof LuaTable) {
                            obj.put(key, toJSONObject((LuaTable) value));
                        } else {
                            obj.put(key, value);
                        }
                    }
                } catch (JSONException e) {
                    System.out.println(e.getMessage());
                }
            }
        }
        return obj;
    }

    private LuaValue toLuaTable(JSONObject obj) {
        LuaValue result = LuaValue.NIL;

        if (obj != null) {
            result = new LuaTable();
            if (obj.size() > 0) {
                for (String key : obj.keySet()) {
                    final Object value = obj.get(key);
                    result.set(key, toLuaValue(value));
                }
            }
        }
        return result;
    }

    private LuaValue toLuaValue(Object value) {
        if (value instanceof String) {
            return LuaValue.valueOf((String) value);
        } else if (value instanceof Integer) {
            return LuaValue.valueOf((Integer) value);
        } else if (value instanceof Long) {
            return LuaValue.valueOf((Long) value);
        } else if (value instanceof Double) {
            return LuaValue.valueOf((Double) value);
        } else if (value instanceof Boolean) {
            return LuaValue.valueOf((Boolean) value);
        } else if (value instanceof JSONObject) {
            return toLuaTable((JSONObject) value);
        } else if (value instanceof JSONArray) {
            return toLuaTable((JSONArray) value);
        } else {
            //TODO 不支持的类型
            return LuaValue.NIL;
        }
    }

    private LuaValue toLuaTable(JSONArray obj) {
        LuaValue result = LuaValue.NIL;

        if (obj != null) {
            result = new LuaTable();//只要不空，就创建一个table
            if (obj.size() > 0) {
                for (int i = 0; i < obj.size(); i++) {
                    final int key = i + 1;
                    final Object value = obj.get(i);
                    result.set(key, toLuaValue(value));
                }
            }
        }
        return result;
    }

    public LuaValue toLuaTable(String jsonString) {
        LuaValue luaTable = LuaValue.NIL;
        try {
            luaTable = toLuaTable(JSONObject.parseObject(jsonString));
        } catch (Exception e) {
            try {
                luaTable = toLuaTable(JSONArray.parseArray(jsonString));
            } catch (JSONException ex1) {
                System.out.println(ex1.getMessage());
            }
        }
        return luaTable;
    }

    private LuaTable mapInclude(LuaTable define) {
        LuaValue[] keys = define.keys();
        LuaTable result = new LuaTable();
        for (LuaValue k : keys) {
            String name = define.get(k).checkstring().toString();
            if (this.includePool.containsKey(name)) {
                result.set(name, this.includePool.get(name));
            }
        }
        return result;
    }

    @Override
    public List<ServiceInfo> queryServices() {
        List<ServiceInfo> ref = new ArrayList<>();
        ServiceInfo s = new ServiceInfo("sys-func", "/function", "脚本扩充函数", null, null);
        s.setServiceType(ServiceTypeEnum.FunctionExt);
        ref.add(s);
        return ref;
    }

    @Override
    public List<FunctionInfo> queryFunctions(String serviceName) {

        List<RequestArg> rq = new ArrayList<>();
        RequestArg ra = new RequestArg();
        ra.setName("name");
        ra.setDataType(DataTypeEnum.STRING);
        ra.setRequestType(RequestArgEnum.RequestBody);
        rq.add(ra);

        List<MetadataField> rp = new ArrayList<>();
        MetadataField mf = new MetadataField();
        mf.setName("code");
        mf.setDataType(DataTypeEnum.INT);
        MetadataField mf2 = new MetadataField();
        mf2.setName("data");
        mf2.setDataType(DataTypeEnum.OBJECT);

        List<MetadataField> l1 = new ArrayList<>();
        MetadataField cmf1 = new MetadataField();
        cmf1.setName("msg");
        cmf1.setDataType(DataTypeEnum.STRING);
        l1.add(cmf1);

        mf2.setChildField(l1);
        rp.add(mf2);
        MetadataField mf3 = new MetadataField();
        mf3.setName("msg");
        mf3.setDataType(DataTypeEnum.STRING);
        rp.add(mf3);

        return this.pool.keySet().stream().map(luaValue -> new FunctionInfo(
                luaValue,
                "/function/call/" + luaValue,
                "",
                RequestMethod.POST,
                rq,
                rp
        )).collect(Collectors.toList());
    }
}
